using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace Xfinity.Maths.Geometry
{
    /// <summary>
    /// A triangle.
    /// </summary>
    public struct Triangle
    {
        /// <summary>
        /// Returns a textual representation of the triangle.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> containing a textual representation of the triangle.
        /// </returns>
        public override string ToString()
        {
            return String.Format("A: {0}; B: {1}; C: {2}; Normal: {3}.", A, B, C, Normal());
        }
        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        public override int GetHashCode()
        {
            return A.GetHashCode() ^ B.GetHashCode() ^ C.GetHashCode();
        }
        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <param name="obj">Another object to compare to.</param>
        /// <returns>
        /// true if obj and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            if (obj is Triangle)
            {
                Triangle t = (Triangle)obj;
                if (this.A == t.A && this.B == t.B && this.C == t.C)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// A boundary point.
        /// </summary>
        public Vector3 A, B, C;

        /// <summary>
        /// Constructs a new triangle.
        /// </summary>
        /// <param name="a">The first point.</param>
        /// <param name="b">The second point.</param>
        /// <param name="c">The third point.</param>
        public Triangle(Vector3 a, Vector3 b, Vector3 c)
        {
            A = a;
            B = b;
            C = c;
        }

        /// <summary>
        /// Computes the normal of the triangle.
        /// </summary>
        /// <returns>The normal of the triangle.</returns>
        public Vector3 Normal()
        {
            Vector3 ab, ac;
            ab = B - A;
            ac = C - A;
            Vector3 normal;
            Vector3.Cross(ref ab, ref ac, out normal);
            normal.Normalize();
            return normal;
        }

        /// <summary>
        /// Computes the normal, as well as two bounding line segments.
        /// </summary>
        /// <param name="ab">A bounding edge.</param>
        /// <param name="ac">A bounding edge.</param>
        /// <returns>The normal of the triangle.</returns>
        public void Normal(out Vector3 ab, out Vector3 ac, out Vector3 normal)
        {
            Vector3.Subtract(ref B, ref A, out ab);
            Vector3.Subtract(ref C, ref A, out ac);
            Vector3.Cross(ref ab, ref ac, out normal);

            normal.Normalize();
        }

        /// <summary>
        /// Tests for intersection.
        /// </summary>
        /// <param name="ray">The ray to test against.</param>
        /// <returns>The results of the intersection test. Will be null if there is no hit.</returns>
        public Vector4? Intersects(Ray ray)
        {
            Vector4? f;
            Intersects(ref ray, out f);
            return f;
        }

        /// <summary>
        /// Checks to see if a triangle blocks the given ray.
        /// </summary>
        /// <param name="ray">The ray to test.</param>
        /// <returns>Whether or not the triangle blocks the ray.</returns>
        public bool Blocks(Ray ray)
        {
            bool result;
            Blocks(ref ray, out result);
            return result;
        }

        /// <summary>
        /// Checks to see if a triangle blocks the given ray.
        /// </summary>
        /// <param name="ray">The ray.</param>
        /// <param name="result">Wether or not the triangle blocks the ray.</param>
        public void Blocks(ref Ray ray, out bool result)
        {
            Vector3 qp = -ray.Direction;
            Vector3 ab, ac;
            Vector3 normal;
            float d;

            Normal(out ab, out ac, out normal);
            Vector3.Dot(ref qp, ref normal, out d);
            if (d <= 0)
            {
                result = false;
                return;
            }

            Vector3 ap;
            Vector3 e;
            float v;

            Vector3.Subtract(ref ray.Position, ref A, out ap);
            Vector3.Cross(ref qp, ref ap, out e);
            Vector3.Dot(ref ac, ref e, out v);

            if (v < 0 || v > d)
            {
                result = false;
                return;
            }

            float w;
            Vector3.Dot(ref ab, ref e, out w);
            w = -w;

            if (w < 0 || v + w > d)
            {
                result = false;
                return;
            }
            result = true;
            return;
        }
        /// <summary>
        /// Tests for intersection.
        /// </summary>
        /// <param name="ray">The ray to test against.</param>
        /// <param name="result">The results of the intersection test. Will be null if there is no hit.</param>
        public void Intersects(ref Ray ray, out Vector4? result)
        {
            Vector3 qp = -ray.Direction;
            Vector3 ab, ac;
            Vector3 normal;
            Normal(out ab, out ac, out normal);

            float d;
            Vector3.Dot(ref qp, ref normal, out d);
            if (d <= 0)
            {
                result = null;
                return;
            }

            Vector3 ap;
            Vector3.Subtract(ref ray.Position, ref A, out ap);

            float t;
            Vector3.Dot(ref ap, ref normal, out t);

            Vector3 e;
            Vector3.Cross(ref qp, ref ap, out e);

            float v;
            Vector3.Dot(ref ac, ref e, out v);
            if (v < 0 || v > d)
            {
                result = null;
                return;
            }

            float w;
            Vector3.Dot(ref ab, ref e, out w);
            w = -w;

            if (w < 0 || v + w > d)
            {
                result = null;
                return;
            }

            //From here on in, we know that the ray intersects the triangle.

            //precalculate divisor
            float ood = 1 / d;

            Vector4 ret = new Vector4();

            ret.W = t * ood;

            ret.Y = v * ood;
            ret.Z = w * ood;
            ret.X = 1 - ret.Y - ret.Z;
            result = ret;
        }
    }
}